/*
 * Copyright (C) 2010 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.zxing.qrcode.camera;

import android.os.IBinder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * This class is used to activate the weak light on some camera phones (not flash) in order to illuminate surfaces for
 * scanning. There is no official way to do this, but, classes which allow access to this function still exist on some
 * devices. This therefore proceeds through a great deal of reflection.
 * 
 * See <a href= "http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/" >
 * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness- programatically/</a> and <a href=
 * "http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java" >
 * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo /DroidLED.java</a>. Thanks to Ryan Alford
 * for pointing out the availability of this class.
 */
final class FlashlightManager
{
    
    private static final Logger log = Logger.getLogger(FlashlightManager.class);
    
    private static final Object iHardwareService;
    
    private static final Method setFlashEnabledMethod;
    static
    {
        log.setLevel(Level.OFF);
        iHardwareService = getHardwareService();
        setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);
        if (iHardwareService == null)
        {
            log.trace("This device does supports control of a flashlight");
        }
        else
        {
            log.trace("This device does not support control of a flashlight");
        }
    }
    
    private FlashlightManager()
    {
    }
    
    private static Object getHardwareService()
    {
        Class<?> serviceManagerClass = maybeForName("android.os.ServiceManager");
        if (serviceManagerClass == null)
        {
            return null;
        }
        
        Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class);
        if (getServiceMethod == null)
        {
            return null;
        }
        
        Object hardwareService = invoke(getServiceMethod, null, "hardware");
        if (hardwareService == null)
        {
            return null;
        }
        
        Class<?> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub");
        if (iHardwareServiceStubClass == null)
        {
            return null;
        }
        
        Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface", IBinder.class);
        if (asInterfaceMethod == null)
        {
            return null;
        }
        
        return invoke(asInterfaceMethod, null, hardwareService);
    }
    
    private static Method getSetFlashEnabledMethod(Object iHardwareService)
    {
        if (iHardwareService == null)
        {
            return null;
        }
        Class<?> proxyClass = iHardwareService.getClass();
        return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class);
    }
    
    private static Class<?> maybeForName(String name)
    {
        try
        {
            return Class.forName(name);
        }
        catch (ClassNotFoundException cnfe)
        {
            // OK
            return null;
        }
        catch (RuntimeException re)
        {
            log.warn("Unexpected error while finding class " + name, re);
            return null;
        }
    }
    
    private static Method maybeGetMethod(Class<?> clazz, String name, Class<?>... argClasses)
    {
        try
        {
            return clazz.getMethod(name, argClasses);
        }
        catch (NoSuchMethodException nsme)
        {
            // OK
            return null;
        }
        catch (RuntimeException re)
        {
            log.warn("Unexpected error while finding method " + name, re);
            return null;
        }
    }
    
    private static Object invoke(Method method, Object instance, Object... args)
    {
        try
        {
            return method.invoke(instance, args);
        }
        catch (IllegalAccessException e)
        {
            log.warn("Unexpected error while invoking " + method, e);
            return null;
        }
        catch (InvocationTargetException e)
        {
            log.warn("Unexpected error while invoking " + method, e.getCause());
            return null;
        }
        catch (RuntimeException re)
        {
            log.warn("Unexpected error while invoking " + method, re);
            return null;
        }
    }
    
    static void enableFlashlight()
    {
        setFlashlight(true);
    }
    
    static void disableFlashlight()
    {
        setFlashlight(false);
    }
    
    private static void setFlashlight(boolean active)
    {
        if (iHardwareService != null)
        {
            invoke(setFlashEnabledMethod, iHardwareService, active);
        }
    }
    
}
